// 2013年版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

// 此文件实现作用域。

package types

import (
	"bytes"
	"fmt"
	"go/token"
	"io"
	"sort"
	"strings"
)

// 作用域维护一组对象及其包含对象的链接
// （父）和包含（子）作用域。可以插入对象
// 然后按名字向上看。作用域的零值是一个随时可用的值
// 空范围。
type Scope struct {
	parent   *Scope
	children []*Scope
	elems    map[string]Object // 懒散地分配
	pos, end token.Pos         // 范围和程度；可能无效
	comment  string            // 仅用于调试
	isFunc   bool              // 如果这是功能范围，则设置（仅限内部使用）
}

// NewScope返回给定父对象中包含的新的空范围
// 范围（如有）。该注释仅用于调试。
func NewScope(parent *Scope, pos, end token.Pos, comment string) *Scope {
	s := &Scope{parent, nil, nil, pos, end, comment, false}
	// 不要将子对象添加到宇宙范围！
	if parent != nil && parent != Universe {
		parent.children = append(parent.children, s)
	}
	return s
}

// 父级返回作用域的包含（父）作用域。
func (s *Scope) Parent() *Scope { return s.parent }

// Len返回作用域元素的数量。
func (s *Scope) Len() int { return len(s.elems) }

// Names按排序顺序返回作用域的元素名称。
func (s *Scope) Names() []string {
	names := make([]string, len(s.elems))
	i := 0
	for name := range s.elems {
		names[i] = name
		i++
	}
	sort.Strings(names)
	return names
}

// NumChildren返回嵌套在s中的作用域数。
func (s *Scope) NumChildren() int { return len(s.children) }

// Child返回0<=i<NumChildren（）的第i个子范围。
func (s *Scope) Child(i int) *Scope { return s.children[i] }

// Lookup返回作用域s中具有给定名称的对象（如果
// 客体存在；否则结果为零。
func (s *Scope) Lookup(name string) Object {
	return s.elems[name]
}

// LookupParent遵循以s开头的作用域父链，直到
// 它查找查找（名称）返回非nil对象的范围，然后
// 返回该范围和对象。如果提供了有效的位置pos，
// 仅考虑在pos处或之前声明的对象。
// 如果不存在这样的范围和对象，结果是（nil，nil）。
// None
// 注意，如果
// 对象已插入到作用域中，并且在该作用域中已具有父对象
// 时间（见插入）。这仅适用于点导入的对象
// 其范围是导出它们的包的范围。
func (s *Scope) LookupParent(name string, pos token.Pos) (*Scope, Object) {
	for ; s != nil; s = s.parent {
		if obj := s.elems[name]; obj != nil && (!pos.IsValid() || obj.scopePos() <= pos) {
			return s, obj
		}
	}
	return nil, nil
}

// Insert尝试将对象obj插入范围s。
// 如果s已包含具有alt的替代对象
// 相同的名称，Insert使s保持不变并返回alt。
// 否则，它将插入obj，设置对象的父范围
// 如果尚未设置，则返回nil。
func (s *Scope) Insert(obj Object) Object {
	name := obj.Name()
	if alt := s.elems[name]; alt != nil {
		return alt
	}
	if s.elems == nil {
		s.elems = make(map[string]Object)
	}
	s.elems[name] = obj
	if obj.Parent() == nil {
		obj.setParent(s)
	}
	return nil
}

// 挤压通过添加全部与父作用域p合并
// s到p的对象，将s的所有子对象添加到
// p的子对象，并从p的子对象中移除s。
// 为s中的每个对象obj调用函数f，其中
// 在p中有一个对象alt。应在之后丢弃
// 被压扁的。
func (s *Scope) squash(err func(obj, alt Object)) {
	p := s.parent
	assert(p != nil)
	for _, obj := range s.elems {
		obj.setParent(nil)
		if alt := p.Insert(obj); alt != nil {
			err(obj, alt)
		}
	}

	j := -1 // 体育儿童的s指数
	for i, ch := range p.children {
		if ch == s {
			j = i
			break
		}
	}
	assert(j >= 0)
	k := len(p.children) - 1
	p.children[j] = p.children[k]
	p.children = p.children[:k]

	p.children = append(p.children, s.children...)

	s.children = nil
	s.elems = nil
}

// Pos和End描述作用域的源代码范围[Pos，End]。
// 只有在检查了类型后，才能保证结果有效
// AST具有完整的位置信息。范围未定义
// 用于宇宙和包范围。
func (s *Scope) Pos() token.Pos { return s.pos }
func (s *Scope) End() token.Pos { return s.end }

// 包含报告pos是否在范围内。
// 仅当类型已检查时，结果才保证有效
// AST具有完整的位置信息。
func (s *Scope) Contains(pos token.Pos) bool {
	return s.pos <= pos && pos < s.end
}

// Innermost返回包含
// 位置。如果位置不在任何范围内，则结果为零。
// 对于宇宙范围，结果也是零。
// 仅当类型已检查时，结果才保证有效
// AST具有完整的位置信息。
func (s *Scope) Innermost(pos token.Pos) *Scope {
	// 包作用域没有扩展数据块，因为它们可能是
	// 不连续，因此迭代包的文件。
	if s.parent == Universe {
		for _, s := range s.children {
			if inner := s.Innermost(pos); inner != nil {
				return inner
			}
		}
	}

	if s.Contains(pos) {
		for _, s := range s.children {
			if s.Contains(pos) {
				return s.Innermost(pos)
			}
		}
		return s
	}
	return nil
}

// WriteTo将作用域的字符串表示形式写入w，
// 使用按名称排序的范围元素。
// 缩进级别由n>=0控制，其中
// n==0表示无缩进。
// 如果设置了recurse，它还会写入嵌套（子）作用域。
func (s *Scope) WriteTo(w io.Writer, n int, recurse bool) {
	const ind = ".  "
	indn := strings.Repeat(ind, n)

	fmt.Fprintf(w, "%s%s scope %p {\n", indn, s.comment, s)

	indn1 := indn + ind
	for _, name := range s.Names() {
		fmt.Fprintf(w, "%s%s\n", indn1, s.elems[name])
	}

	if recurse {
		for _, s := range s.children {
			s.WriteTo(w, n+1, recurse)
		}
	}

	fmt.Fprintf(w, "%s}\n", indn)
}

// String返回用于调试的作用域的字符串表示形式。
func (s *Scope) String() string {
	var buf bytes.Buffer
	s.WriteTo(&buf, 0, false)
	return buf.String()
}
